/*
 *  Copyright 2012 yura.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package org.opu.odg.dg.logicbeans;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import static org.opu.odg.dg.util.ArchiveUtil.*;

/**
 *
 * @author yura
 */
public class ArchiveFileTemplateForDG implements TemplateFile {
    private static final DocumentBuilderFactory domFactory;
    private static final XPathFactory xPathFactory;

    static {
        domFactory = DocumentBuilderFactory.newInstance();
        xPathFactory = XPathFactory.newInstance();
        domFactory.setNamespaceAware(true);
    }

    private ChangeableEntryDeterminator entryDeterminator;
    private Map<String, byte[]> archive;
    private Map<String, String> encodings;

    public ArchiveFileTemplateForDG(ChangeableEntryDeterminator entryDeterminator) {
        this.entryDeterminator = entryDeterminator;
    }

    @Override
    public void load(InputStream is) throws IOException {
        archive = readArchive(is);
    }

    @Override
    public Map<String, String> getTemplateFileContent() {
        Collection<String> entrys = entryDeterminator.getChangeableEntrys(archive);
        Map<String, String> map = new HashMap<String, String>();
        encodings = new HashMap<String, String>();

        SingleFileTemplateForDG sf = new SingleFileTemplateForDG();
        for (String string : entrys) {
            byte[] get = archive.get(string);
            if (get == null && string.startsWith("/")) {
                get = archive.get(string.substring(1));
            }
            if (get == null) {
                throw new IllegalArgumentException("Document doesn't contain "
                        + "all processing file(s). (" + string + ")");
            }
            try {
                sf.load(new ByteArrayInputStream(get));
                encodings.put(string, sf.getEncoding());
                map.put(string, sf.getContent());
            } catch (IOException ex) {
                throw new IllegalArgumentException();
            }
        }

        return map;
    }

    @Override
    public void setResolvedFileContent(Map<String, String> data) {
        for (String string : data.keySet()) {
            String value = data.get(string);
            String encoding = encodings.get(string);

            if (!archive.containsKey(string) && string.startsWith("/")) {
                string = string.substring(1);
            }

            if (!archive.containsKey(string)) {
                throw new IllegalArgumentException(string + " is unsupported archive entry.");
            }

            try {
                archive.put(string, value.getBytes(encoding));
            } catch (UnsupportedEncodingException ex) {
                throw new IllegalArgumentException(ex);
            }
        }
    }

    @Override
    public void store(OutputStream os) throws IOException {
        writeArchive(archive, os);
    }

    public static interface ChangeableEntryDeterminator extends Serializable {

        Collection<String> getChangeableEntrys(Map<String, byte[]> archive);
        
        String toPlaineText();

    }

    @Root(name="FileNames")
    public static class DirectFileNamesEntryDeterminator 
            implements ChangeableEntryDeterminator {

        @ElementList(inline=true)
        private ArrayList<String> collection;

        private DirectFileNamesEntryDeterminator() {
        }

        public DirectFileNamesEntryDeterminator(List<String> collection) {
            this.collection = new ArrayList(collection);
        }

        @Override
        public Collection<String> getChangeableEntrys(Map<String, byte[]> archive) {
            return collection;
        }

        @Override
        public String toPlaineText() {
            StringBuilder sb = new StringBuilder();
            for (Iterator<String> it = collection.iterator(); it.hasNext();) {
                sb.append(it.next());
                if (it.hasNext()) {
                    sb.append('\n');
                }
            }
            return sb.toString();
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final DirectFileNamesEntryDeterminator other = (DirectFileNamesEntryDeterminator) obj;
            if (this.collection != other.collection && (this.collection == null || !this.collection.equals(other.collection))) {
                return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            int hash = 3;
            hash = 79 * hash + (this.collection != null ? this.collection.hashCode() : 0);
            return hash;
        }
    }

    @Root(name="XpathEntry")
    public static class XpathEntryDeterminator
            implements ChangeableEntryDeterminator {

        @Attribute
        private String metaFileName;
        @ElementList
        private ArrayList<String> xpatExpressions;

        private XpathEntryDeterminator() {
        }

        public XpathEntryDeterminator(String metaFileName, List<String> xpatExpressions) {
            this.metaFileName = metaFileName;
            this.xpatExpressions = new ArrayList(xpatExpressions);
        }

        @Override
        public Collection<String> getChangeableEntrys(Map<String, byte[]> archive) {
            List<String> fnames = new ArrayList<String>();

            byte[] metaFileData = archive.get(metaFileName);
            if (metaFileData == null || metaFileData.length == 0) {
                throw new IllegalArgumentException("Document doesn't contain "
                        + "file " + metaFileName + " where storing all mapping.");
            }

            try {
                DocumentBuilder builder = domFactory.newDocumentBuilder();
                Document doc = builder.parse(new ByteArrayInputStream(metaFileData));
                XPath xpath = xPathFactory.newXPath();
                
                // XPath Query for showing all nodes value ("//person/*/text()")
                for(String xpatExpression : xpatExpressions) {
                    XPathExpression expr = xpath.compile(xpatExpression);
                    Object result = expr.evaluate(doc, XPathConstants.NODESET);
                    
                    NodeList nodes = (NodeList) result;
                    for (int i = 0; i < nodes.getLength(); i++) {
                        fnames.add(nodes.item(i).getNodeValue());
                    }
                }
            } catch (Exception ex) {
                throw new IllegalArgumentException(ex);
            }

            return fnames;
        }

        @Override
        public String toPlaineText() {
            StringBuilder sb = new StringBuilder().append(metaFileName).append('\n');
            for (Iterator<String> it = xpatExpressions.iterator(); it.hasNext();) {
                sb.append(it.next());
                if (it.hasNext()) {
                    sb.append('\n');
                }
            }
            return sb.toString();
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final XpathEntryDeterminator other = (XpathEntryDeterminator) obj;
            if ((this.metaFileName == null) ? (other.metaFileName != null) : !this.metaFileName.equals(other.metaFileName)) {
                return false;
            }
            if (this.xpatExpressions != other.xpatExpressions && (this.xpatExpressions == null || !this.xpatExpressions.equals(other.xpatExpressions))) {
                return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 97 * hash + (this.metaFileName != null ? this.metaFileName.hashCode() : 0);
            hash = 97 * hash + (this.xpatExpressions != null ? this.xpatExpressions.hashCode() : 0);
            return hash;
        }
    }

}
